/**
 * Copyright (c) 2004-2011 QOS.ch
 * All rights reserved.
 * <p>
 * Permission is hereby granted, free  of charge, to any person obtaining
 * a  copy  of this  software  and  associated  documentation files  (the
 * "Software"), to  deal in  the Software without  restriction, including
 * without limitation  the rights to  use, copy, modify,  merge, publish,
 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
 * permit persons to whom the Software  is furnished to do so, subject to
 * the following conditions:
 * <p>
 * The  above  copyright  notice  and  this permission  notice  shall  be
 * included in all copies or substantial portions of the Software.
 * <p>
 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package cn.chiship.sdk.core.util.formatter;

import java.util.HashMap;
import java.util.Map;

/**
 * {}占位符工具
 *
 * @author LIJIAN
 */
public class MessageFormatter {

	static final int NUMBER_TWO = 2;
	static final char DELIM_START = '{';
	static final char DELIM_STOP = '}';
	static final String DELIM_STR = "{}";

	private static final char ESCAPE_CHAR = '\\';

	private MessageFormatter() {

	}

	public static String format(String messagePattern, Object arg) {
		return arrayFormat(messagePattern, new Object[] { arg }).getMessage();
	}

	public static String format(final String messagePattern, Object arg1, Object arg2) {
		return arrayFormat(messagePattern, new Object[] { arg1, arg2 }).getMessage();
	}

	public static String format(final String messagePattern, Object... arg) {
		Object[] objects = new Object[arg.length];
		for (Integer i = 0; i < arg.length; i++) {
			objects[i] = arg[i];
		}
		return arrayFormat(messagePattern, objects).getMessage();
	}

	static final Throwable getThrowableCandidate(Object[] argArray) {
		if (argArray == null || argArray.length == 0) {
			return null;
		}

		final Object lastEntry = argArray[argArray.length - 1];
		if (lastEntry instanceof Throwable) {
			return (Throwable) lastEntry;
		}
		return null;
	}

	public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {
		Throwable throwableCandidate = getThrowableCandidate(argArray);
		Object[] args = argArray;
		if (throwableCandidate != null) {
			args = trimmedCopy(argArray);
		}
		return arrayFormat(messagePattern, args, throwableCandidate);
	}

	private static Object[] trimmedCopy(Object[] argArray) {
		if (argArray == null || argArray.length == 0) {
			throw new IllegalStateException("non-sensical empty or null argument array");
		}
		final int trimemdLen = argArray.length - 1;
		Object[] trimmed = new Object[trimemdLen];
		System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);
		return trimmed;
	}

	public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray,
			Throwable throwable) {

		if (messagePattern == null) {
			return new FormattingTuple(null, argArray, throwable);
		}

		if (argArray == null) {
			return new FormattingTuple(messagePattern);
		}

		int i = 0;
		int j;
		StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);

		int l;
		for (l = 0; l < argArray.length; l++) {

			j = messagePattern.indexOf(DELIM_STR, i);

			if (j == -1) {
				if (i == 0) {
					return new FormattingTuple(messagePattern, argArray, throwable);
				}
				else {
					sbuf.append(messagePattern, i, messagePattern.length());
					return new FormattingTuple(sbuf.toString(), argArray, throwable);
				}
			}
			else {
				if (isEscapedDelimeter(messagePattern, j)) {
					if (!isDoubleEscaped(messagePattern, j)) {
						l--;
						sbuf.append(messagePattern, i, j - 1);
						sbuf.append(DELIM_START);
						i = j + 1;
					}
					else {
						sbuf.append(messagePattern, i, j - 1);
						deeplyAppendParameter(sbuf, argArray[l], new HashMap<>(16));
						i = j + 2;
					}
				}
				else {
					sbuf.append(messagePattern, i, j);
					deeplyAppendParameter(sbuf, argArray[l], new HashMap<>(16));
					i = j + 2;
				}
			}
		}
		sbuf.append(messagePattern, i, messagePattern.length());
		return new FormattingTuple(sbuf.toString(), argArray, throwable);
	}

	static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {

		if (delimeterStartIndex == 0) {
			return false;
		}
		char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
		return potentialEscape == ESCAPE_CHAR;
	}

	static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
		return delimeterStartIndex >= NUMBER_TWO
				&& messagePattern.charAt(delimeterStartIndex - NUMBER_TWO) == ESCAPE_CHAR;

	}

	private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {
		if (o == null) {
			sbuf.append("null");
			return;
		}
		if (!o.getClass().isArray()) {
			safeObjectAppend(sbuf, o);
		}
		else {
			if (o instanceof boolean[]) {
				booleanArrayAppend(sbuf, (boolean[]) o);
			}
			else if (o instanceof byte[]) {
				byteArrayAppend(sbuf, (byte[]) o);
			}
			else if (o instanceof char[]) {
				charArrayAppend(sbuf, (char[]) o);
			}
			else if (o instanceof short[]) {
				shortArrayAppend(sbuf, (short[]) o);
			}
			else if (o instanceof int[]) {
				intArrayAppend(sbuf, (int[]) o);
			}
			else if (o instanceof long[]) {
				longArrayAppend(sbuf, (long[]) o);
			}
			else if (o instanceof float[]) {
				floatArrayAppend(sbuf, (float[]) o);
			}
			else if (o instanceof double[]) {
				doubleArrayAppend(sbuf, (double[]) o);
			}
			else {
				objectArrayAppend(sbuf, (Object[]) o, seenMap);
			}
		}
	}

	private static void safeObjectAppend(StringBuilder sbuf, Object o) {
		try {
			String oAsString = o.toString();
			sbuf.append(oAsString);
		}
		catch (Exception t) {
			sbuf.append("[FAILED toString()]");
		}

	}

	private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {
		sbuf.append('[');
		if (!seenMap.containsKey(a)) {
			seenMap.put(a, null);
			final int len = a.length;
			for (int i = 0; i < len; i++) {
				deeplyAppendParameter(sbuf, a[i], seenMap);
				if (i != len - 1) {
					sbuf.append(", ");
				}
			}
			// allow repeats in siblings
			seenMap.remove(a);
		}
		else {
			sbuf.append("...");
		}
		sbuf.append(']');
	}

	private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}
		}
		sbuf.append(']');
	}

	private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}
		}
		sbuf.append(']');
	}

	private static void charArrayAppend(StringBuilder sbuf, char[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}
		}
		sbuf.append(']');
	}

	private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}
		}
		sbuf.append(']');
	}

	private static void intArrayAppend(StringBuilder sbuf, int[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}

		}
		sbuf.append(']');
	}

	private static void longArrayAppend(StringBuilder sbuf, long[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}

		}
		sbuf.append(']');
	}

	private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}

		}
		sbuf.append(']');
	}

	private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
		sbuf.append('[');
		final int len = a.length;
		for (int i = 0; i < len; i++) {
			sbuf.append(a[i]);
			if (i != len - 1) {
				sbuf.append(", ");
			}
		}
		sbuf.append(']');
	}

}
